/**
 * @license
 * jQuery Tools 1.2.5 Dateinput - <input type="date" /> for humans
 *
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 *
 * http://flowplayer.org/tools/form/dateinput/
 *
 * Since: Mar 2010
 * Date:    Wed Sep 22 06:02:10 2010 +0000
 */
(function($) {

	/* TODO:
		 preserve today highlighted
	*/

	$.tools = $.tools || {version: '1.2.5'};

	var instances = [],
		 tool,

		 // h=72, j=74, k=75, l=76, down=40, left=37, up=38, right=39
		 KEYS = [75, 76, 38, 39, 74, 72, 40, 37],
		 LABELS = {};

	tool = $.tools.dateinput = {

		conf: {
			format: 'mm/dd/yy',
			selectors: false,
			yearRange: [-5, 5],
			lang: 'zh',
			offset: [0, 0],
			speed: 0,
			firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
			min: undefined,
			max: undefined,
			trigger: false,

			css: {

				prefix: 'cal',
				input: 'date',

				// ids
				root: 0,
				head: 0,
				title: 0,
				prev: 0,
				next: 0,
				month: 0,
				year: 0,
				days: 0,

				body: 0,
				weeks: 0,
				today: 0,
				current: 0,

				// classnames
				week: 0,
				off: 0,
				sunday: 0,
				focus: 0,
				disabled: 0,
				trigger: 0
			}
		},

		localize: function(language, labels) {
			$.each(labels, function(key, val) {
				labels[key] = val.split(",");
			});
			LABELS[language] = labels;
		}

	};

	tool.localize("zh", {
		months:        '一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月',
		shortMonths:   '一,二,三,四,五,六,七,八,九,十,十一,十二',
		days:          '日,一,二,三,四,五,六',
		shortDays:     '日,一,二,三,四,五,六'
	});


//{{{ private functions


	// @return amount of days in certain month
	function dayAm(year, month) {
		return 32 - new Date(year, month, 32).getDate();
	}

	function zeropad(val, len) {
		val = '' + val;
		len = len || 2;
		while (val.length < len) { val = "0" + val; }
		return val;
	}

	// thanks: http://stevenlevithan.com/assets/misc/date.format.js
	var Re = /d{1,4}|m{1,4}|yy(?:yy)?|"[^"]*"|'[^']*'/g, tmpTag = $("<a/>");

	function format(date, fmt, lang) {

	  var d = date.getDate(),
			D = date.getDay(),
			m = date.getMonth(),
			y = date.getFullYear(),

			flags = {
				d:    d,
				dd:   zeropad(d),
				ddd:  LABELS[lang].shortDays[D],
				dddd: LABELS[lang].days[D],
				m:    m + 1,
				mm:   zeropad(m + 1),
				mmm:  LABELS[lang].shortMonths[m],
				mmmm: LABELS[lang].months[m],
				yy:   String(y).slice(2),
				yyyy: y
			};

		var ret = fmt.replace(Re, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});

		// a small trick to handle special characters
		return tmpTag.html(ret).html();

	}

	function integer(val) {
		return parseInt(val, 10);
	}

	function isSameDay(d1, d2)  {
		return d1.getFullYear() === d2.getFullYear() &&
			d1.getMonth() == d2.getMonth() &&
			d1.getDate() == d2.getDate();
	}

	function parseDate(val) {

		if (!val) { return; }
		if (val.constructor == Date) { return val; }

		if (typeof val == 'string') {

			// rfc3339?
			var els = val.split("-");
			if (els.length == 3) {
				return new Date(integer(els[0]), integer(els[1]) -1, integer(els[2]));
			}

			// invalid offset
			if (!/^-?\d+$/.test(val)) { return; }

			// convert to integer
			val = integer(val);
		}

		var date = new Date();
		date.setDate(date.getDate() + val);
		return date;
	}

//}}}


	function Dateinput(input, conf)  {

		// variables
		var self = this,
			 now = new Date(),
			 css = conf.css,
			 labels = LABELS[conf.lang],
			 root = $("#" + css.root),
			 title = root.find("#" + css.title),
			 trigger,
			 pm, nm,
			 currYear, currMonth, currDay,
			 value = input.attr("data-value") || conf.value || input.val(),
			 min = input.attr("min") || conf.min,
			 max = input.attr("max") || conf.max,
			 opened;

		// zero min is not undefined
		if (min === 0) { min = "0"; }

		// use sane values for value, min & max
		value = parseDate(value) || now;
		min   = parseDate(min || conf.yearRange[0] * 365);
		max   = parseDate(max || conf.yearRange[1] * 365);


		// check that language exists
		if (!labels) { throw "Dateinput: invalid language: " + conf.lang; }

		// Replace built-in date input: NOTE: input.attr("type", "text") throws exception by the browser
		if (input.attr("type") == 'date') {
			var tmp = $("<input/>");

			$.each("class,disabled,id,maxlength,name,readonly,required,size,style,tabindex,title,value".split(","), function(i, attr)  {
				tmp.attr(attr, input.attr(attr));
			});
			input.replaceWith(tmp);
			input = tmp;
		}
		input.addClass(css.input);

		var fire = input.add(self);

		// construct layout
		if (!root.length) {

			// root
			root = $('<div><div><a/><div/><a/></div><div><div/><div/></div></div>')
				.hide().css({position: 'absolute'}).attr("id", css.root);

			// elements
			root.children()
				.eq(0).attr("id", css.head).end()
				.eq(1).attr("id", css.body).children()
					.eq(0).attr("id", css.days).end()
					.eq(1).attr("id", css.weeks).end().end().end()
				.find("a").eq(0).attr("id", css.prev).end().eq(1).attr("id", css.next);

			// title
			title = root.find("#" + css.head).find("div").attr("id", css.title);

			// year & month selectors
			if (conf.selectors) {
				var monthSelector = $("<select/>").attr("id", css.month),
					 yearSelector = $("<select/>").attr("id", css.year);
				title.html(monthSelector.add(yearSelector));
			}

			// day titles
			var days = root.find("#" + css.days);

			// days of the week
			for (var d = 0; d < 7; d++) {
				days.append($("<span/>").text(labels.shortDays[(d + conf.firstDay) % 7]));
			}

			$("body").append(root);
		}


		// trigger icon
		if (conf.trigger) {
			trigger = $("<a/>").attr("href", "#").addClass(css.trigger).click(function(e)  {
				self.show();
				return e.preventDefault();
			}).insertAfter(input);
		}


		// layout elements
		var weeks = root.find("#" + css.weeks);
		yearSelector = root.find("#" + css.year);
		monthSelector = root.find("#" + css.month);


//{{{ pick

		function select(date, conf, e) {

			// current value
			value 	 = date;
			currYear  = date.getFullYear();
			currMonth = date.getMonth();
			currDay	 = date.getDate();


			// change
			e = e || $.Event("api");
			e.type = "change";

			fire.trigger(e, [date]);
			if (e.isDefaultPrevented()) { return; }

			// formatting
			input.val(format(date, conf.format, conf.lang));

			// store value into input
			input.data("date", date);

			self.hide(e);
		}
//}}}


//{{{ onShow

		function onShow(ev) {

			ev.type = "onShow";
			fire.trigger(ev);

			$(document).bind("keydown.d", function(e) {

				if (e.ctrlKey) { return true; }
				var key = e.keyCode;

				// backspace clears the value
				if (key == 8) {
					input.val("");
					return self.hide(e);
				}

				// esc key
				if (key == 27) { return self.hide(e); }

				if ($(KEYS).index(key) >= 0) {

					if (!opened) {
						self.show(e);
						return e.preventDefault();
					}

					var days = $("#" + css.weeks + " a"),
						 el = $("." + css.focus),
						 index = days.index(el);

					el.removeClass(css.focus);

					if (key == 74 || key == 40) { index += 7; }
					else if (key == 75 || key == 38) { index -= 7; }
					else if (key == 76 || key == 39) { index += 1; }
					else if (key == 72 || key == 37) { index -= 1; }


					if (index > 41) {
						 self.addMonth();
						 el = $("#" + css.weeks + " a:eq(" + (index-42) + ")");
					} else if (index < 0) {
						 self.addMonth(-1);
						 el = $("#" + css.weeks + " a:eq(" + (index+42) + ")");
					} else {
						 el = days.eq(index);
					}

					el.addClass(css.focus);
					return e.preventDefault();

				}

				// pageUp / pageDown
				if (key == 34) { return self.addMonth(); }
				if (key == 33) { return self.addMonth(-1); }

				// home
				if (key == 36) { return self.today(); }

				// enter
				if (key == 13) {
					if (!$(e.target).is("select")) {
						$("." + css.focus).click();
					}
				}

				return $([16, 17, 18, 9]).index(key) >= 0;
			});


			// click outside dateinput
			$(document).bind("click.d", function(e) {
				var el = e.target;

				if (!$(el).parents("#" + css.root).length && el != input[0] && (!trigger || el != trigger[0])) {
					self.hide(e);
				}

			});
		}
//}}}


		$.extend(self, {

//{{{  show

			show: function(e) {

				if (input.attr("readonly") || input.attr("disabled") || opened) { return; }

				// onBeforeShow
				e = e || $.Event();
				e.type = "onBeforeShow";
				fire.trigger(e);
				if (e.isDefaultPrevented()) { return; }

				$.each(instances, function() {
					this.hide();
				});

				opened = true;

				// month selector
				monthSelector.unbind("change").change(function() {
					self.setValue(yearSelector.val(), $(this).val());
				});

				// year selector
				yearSelector.unbind("change").change(function() {
					self.setValue($(this).val(), monthSelector.val());
				});

				// prev / next month
				pm = root.find("#" + css.prev).unbind("click").click(function(e) {
					if (!pm.hasClass(css.disabled)) {
						self.addMonth(-1);
					}
					return false;
				});

				nm = root.find("#" + css.next).unbind("click").click(function(e) {
					if (!nm.hasClass(css.disabled)) {
						self.addMonth();
					}
					return false;
				});

				// set date
				self.setValue(value);

				// show calendar
				var pos = input.offset();

				// iPad position fix
				if (/iPad/i.test(navigator.userAgent)) {
					pos.top -= $(window).scrollTop();
				}

				root.css({
					top: pos.top + input.outerHeight({margins: true}) + conf.offset[0],
					left: pos.left + conf.offset[1]
				});

				if (conf.speed) {
					root.show(conf.speed, function() {
						onShow(e);
					});
				} else {
					root.show();
					onShow(e);
				}

				return self;
			},
//}}}


//{{{  setValue

			setValue: function(year, month, day)  {

				// bell (2010-12-6): .addDay()方法的实现是调用setValue
				// 这导致setValue时，day的值可能为0(本月第一天时调用addDay(-1))，此时day应按0理解
				var date = integer(month) >= -1 ?
					// new Date(integer(year), integer(month), integer(day || 1)) : year || value
					new Date(integer(year), integer(month), integer(day === undefined ? 1 : day)) : year || value
				;

				if (date < min) { date = min; }
				else if (date > max) { date = max; }

				year = date.getFullYear();
				month = date.getMonth();
				day = date.getDate();


				// roll year & month
				if (month == -1) {
					month = 11;
					year--;
				} else if (month == 12) {
					month = 0;
					year++;
				}

				if (!opened) {
					select(date, conf);
					return self;
				}

				currMonth = month;
				currYear = year;

				// variables
				var tmp = new Date(year, month, 1 - conf.firstDay), begin = tmp.getDay(),
					 days = dayAm(year, month),
					 prevDays = dayAm(year, month - 1),
					 week;

				// selectors
				if (conf.selectors) {

					// month selector
					monthSelector.empty();
					$.each(labels.months, function(i, m) {
						if (min < new Date(year, i + 1, -1) && max > new Date(year, i, 0)) {
							monthSelector.append($("<option/>").html(m).attr("value", i));
						}
					});

					// year selector
					yearSelector.empty();
					var yearNow = now.getFullYear();

					for (var i = yearNow + conf.yearRange[0];  i < yearNow + conf.yearRange[1]; i++) {
						if (min <= new Date(i + 1, -1, 1) && max > new Date(i, 0, 0)) {
							yearSelector.append($("<option/>").text(i));
						}
					}

					monthSelector.val(month);
					yearSelector.val(year);

				// title
				} else {
					title.html(year + " " + labels.months[month]);
				}

				// populate weeks
				weeks.empty();
				pm.add(nm).removeClass(css.disabled);

				// !begin === "sunday"
				for (var j = !begin ? -7 : 0, a, num; j < (!begin ? 35 : 42); j++) {

					a = $("<a/>");

					if (j % 7 === 0) {
						week = $("<div/>").addClass(css.week);
						weeks.append(week);
					}

					if (j < begin)  {
						a.addClass(css.off);
						num = prevDays - begin + j + 1;
						date = new Date(year, month-1, num);

					} else if (j >= begin + days)  {
						a.addClass(css.off);
						num = j - days - begin + 1;
						date = new Date(year, month+1, num);

					} else  {
						num = j - begin + 1;
						date = new Date(year, month, num);

						// current date
						if (isSameDay(value, date)) {
							a.attr("id", css.current).addClass(css.focus);

						// today
						} else if (isSameDay(now, date)) {
							a.attr("id", css.today);
						}
					}

					// disabled
					if (min && date < min) {
						// a.add(pm).addClass(css.disabled);
						// bell (2010-12-6): 此处原有实现有bug，例如：
						// 显示11月份面板时，如最后可选日期为12月5号，12月6号也在面板中且变灰
						// 但不能因为12月6号已经不能选择，而将翻到12月的按钮也变灰
						a.addClass(css.disabled);
						if (date.getMonth() === month)
							pm.addClass(css.disabled);
					}

					if (max && date > max) {
						// a.add(nm).addClass(css.disabled);
						// bell (2010-12-6): 同上
						a.addClass(css.disabled);
						if (date.getMonth() === month)
							nm.addClass(css.disabled);
					}

					a.attr("href", "#" + num).text(num).data("date", date);

					week.append(a);
				}

				// date picking
				weeks.find("a").click(function(e) {
					var el = $(this);
					if (!el.hasClass(css.disabled)) {
						$("#" + css.current).removeAttr("id");
						el.attr("id", css.current);
						select(el.data("date"), conf, e);
					}
					return false;
				});

				// sunday
				if (css.sunday) {
					weeks.find(css.week).each(function() {
						var beg = conf.firstDay ? 7 - conf.firstDay : 0;
						$(this).children().slice(beg, beg + 1).addClass(css.sunday);
					});
				}

				return self;
			},
	//}}}

			setMin: function(val, fit) {
				min = parseDate(val);
				if (fit && value < min) { self.setValue(min); }
				return self;
			},

			setMax: function(val, fit) {
				max = parseDate(val);
				if (fit && value > max) { self.setValue(max); }
				return self;
			},

			today: function() {
				return self.setValue(now);
			},

			addDay: function(amount) {
				return this.setValue(currYear, currMonth, currDay + (amount || 1));
			},

			addMonth: function(amount) {
				return this.setValue(currYear, currMonth + (amount || 1), currDay);
			},

			addYear: function(amount) {
				return this.setValue(currYear + (amount || 1), currMonth, currDay);
			},

			hide: function(e) {

				if (opened) {

					// onHide
					e = $.Event();
					e.type = "onHide";
					fire.trigger(e);

					$(document).unbind("click.d").unbind("keydown.d");

					// cancelled ?
					if (e.isDefaultPrevented()) { return; }

					// do the hide
					root.hide();
					opened = false;
				}

				return self;
			},

			getConf: function() {
				return conf;
			},

			getInput: function() {
				return input;
			},

			getCalendar: function() {
				return root;
			},

			getValue: function(dateFormat) {
				return dateFormat ? format(value, dateFormat, conf.lang) : value;
			},

			isOpen: function() {
				return opened;
			}

		});

		// callbacks
		$.each(['onBeforeShow','onShow','change','onHide'], function(i, name) {

			// configuration
			if ($.isFunction(conf[name]))  {
				$(self).bind(name, conf[name]);
			}

			// API methods
			self[name] = function(fn) {
				if (fn) { $(self).bind(name, fn); }
				return self;
			};
		});


		// show dateinput & assign keyboard shortcuts
		input.bind("focus click", self.show).keydown(function(e) {

			var key = e.keyCode;

			// open dateinput with navigation keyw
			if (!opened &&  $(KEYS).index(key) >= 0) {
				self.show(e);
				return e.preventDefault();
			}

			// allow tab
			return e.shiftKey || e.ctrlKey || e.altKey || key == 9 ? true : e.preventDefault();

		});

		// initial value
		if (parseDate(input.val())) {
			select(value, conf);
		}

	}

	$.expr[':'].date = function(el) {
		var type = el.getAttribute("type");
		return type && type == 'date' || !!$(el).data("dateinput");
	};


	$.fn.dateinput = function(conf) {

		// already instantiated
		if (this.data("dateinput")) { return this; }

		// configuration
		conf = $.extend(true, {}, tool.conf, conf);

		// CSS prefix
		$.each(conf.css, function(key, val) {
			if (!val && key != 'prefix') {
				conf.css[key] = (conf.css.prefix || '') + (val || key);
			}
		});

		var els;

		this.each(function() {
			var el = new Dateinput($(this), conf);
			instances.push(el);
			var input = el.getInput().data("dateinput", el);
			els = els ? els.add(input) : input;
		});

		return els ? els : this;
	};


}) (jQuery);


